import _app = require('durandal/app');
import _router = require('plugins/router');
import _dialog = require('plugins/dialog');
import _ko = require('knockout');

import DiscoViewModel = require('lib/discoViewModel');

import QuestionInfo = require('entities/questioninfo');
import ArgumentInfo = require('entities/argumentinfo');
import ArgumentEdit = require('argumentedit');

export class Discussion extends DiscoViewModel {

    public displayName: string = 'Discussion';

    public currentQuestion: KnockoutObservable<QuestionInfo>;
    public currentRelated: KnockoutObservableArray<QuestionInfo>;

    public droppable: any = {
        accept: '.argument_general',
        hoverClass: 'hovered',
        activeClass: 'activeDrop',
        activate: (event, ui) => {
            // TODO: Model binding.
            jQuery('span.click').hide();
            jQuery('span.drag').show();
        },
        deactivate: (event, ui) => {
            // TODO: Model binding.
            jQuery('span.drag').hide();
            jQuery('span.click').show();
        },
        drop: (event, ui) => {
            var newType = jQuery(event.target).hasClass('button_new_argument_pro') ? 'pro' : 'con';
            var context = ui.draggable.data('bindingContext');

            // HACK: Need to switch execution thread here to get the cursor reset.
            window.setTimeout(() => context.$data.type(newType), 0);
        }
    };

    public draggable: any = {
        containment: '#view',
        cursor: 'move',
        helper: 'clone',
        zIndex: 100,
        opacity: .5,
        revert: 'invalid',
        //start: (event, ui) => {
        //    ui.helper.removeClass('argument');
        //},
        //stop: (event, ui) => {
        //    ui.helper.addClass('argument');
        //}
    };

    private changeArgumentAssignment: (target: ArgumentInfo, data: any) => void = null;
    private updateArgumentContent: (data: ArgumentInfo) => void = null;

    public addArgument: (argument: ArgumentInfo) => void = null;
    public editArgument: (argument: ArgumentInfo) => void = null;
    public resetArgument: (argument: ArgumentInfo) => void = null;

    constructor() {
        super();

        this.changeArgumentAssignment = (argument: ArgumentInfo, data: any) => {
            var typeId: string = (data == '') ? '2' : (data == 'pro') ? '7' : '8';

            var reference: Disco.Ontology.PostReference = new Disco.Ontology.PostReference();
            reference.Id = argument.referenceId;

            try {
                this.discoContext.PostReferences.attach(reference);
            } catch (error) {
                this.onError(error);
            }
            // HINT: Currently we *just* overwrite any existing reference type without any checks for duplicates!
            // TODO: Define PostType and ReferenceType constraints.
            reference.ReferenceTypeId = typeId;

            this.discoContext.saveChanges().fail((error: any) => {
                // TODO: As a User I want the last action rolled back after an error occured so that I can rely on the current page state.
                this.onError(error);
            });
        };

        this.updateArgumentContent = (data: ArgumentInfo) => {
            if (!data) {
                return;
            }

            var questionId = null;
            var result = this.discoContext.Posts
                .filter((post) => post.Id == questionId, { questionId: data.questionId })
                .include('Content')
                .toArray();

            result.then((posts: Disco.Ontology.Post[]) => {
                posts.forEach((post: Disco.Ontology.Post) => {
                    try {
                        this.discoContext.Content.attach(post.Content);

                        post.Content.Title = data.title();
                        post.Content.Text = data.abstract();
                    } catch (error) {
                        this.onError(error);
                    }
                });

                this.discoContext.saveChanges().fail((error: any) => {
                    // TODO: As a User I want the last action rolled back after an error occured so that I can rely on the current page state.
                    this.onError(error);
                });
            });
        };

        this.addArgument = (argument: ArgumentInfo) => {
            _app.showDialog('viewmodels/argumentedit', argument).then((data: ArgumentInfo) => {
                alert(data);
            });
        }

        this.editArgument = (argument: ArgumentInfo) => {
            _app.showDialog('viewmodels/argumentedit', argument).then((data: ArgumentInfo) => {
                this.updateArgumentContent(data);
            });
        }

        this.resetArgument = (argument: ArgumentInfo) => {
            argument.type('');
        }

        this.currentQuestion = _ko.observable<QuestionInfo>();
        this.currentRelated = _ko.observableArray<QuestionInfo>();
    }

    public canActivate(): boolean {
        var canActivate: boolean = (null == this.parameters);
        return canActivate;
    }

    public activate(parameters: any) {
        super.activate(parameters);

        if (!this.currentQuestion()) {
            console.debug('Data not initialized yet.');
            var promise = this.initDiscussion(parameters)
                .then(data => { this.initReferrers(); })
                .then(data => { this.isLoading(false) });
        } else {
            this.initReferrers();
        }

        
        return true;
    }

    private initDiscussion(questionKey: string): $data.IPromise<Disco.Ontology.Post[]> {
        var questions = _ko.observableArray<QuestionInfo>();

        // HACK: Just satisfy the TypeScript compiler, because we can't reference the 'this' context of an event callback directly.
        var key: string; 
        var query = post => post.Key == /*this.*/key;

        var promise = this.loadQuestions(
            questions, query, { key: questionKey },
            data => new QuestionInfo(data))
            .then(ignore => { this.currentQuestion(questions()[0]) });

        return promise;
    }

    private initReferrers(): $data.IPromise<Disco.Ontology.Post[]> {
        // HACK: Just satisfy the TypeScript compiler, because we can't reference the 'this' context of an event callback directly.
        var referrers: any[];
        var query = post => post.Id in /*this.*/referrers;

        var promise = this.loadQuestions(
            this.currentRelated, query, { referrers: this.getReferrers(this.currentQuestion().referredFrom) },
            data => this.argumentInfoFactory(data))
            .then(ignore => { });

        return promise;
    }

    private argumentInfoFactory(post: Disco.Ontology.Post): QuestionInfo {
        var argumentInfo = new ArgumentInfo(this.currentQuestion(), post);
        argumentInfo.onTypeChange = this.changeArgumentAssignment;
        return argumentInfo;
    }

    private static getBoolean(): boolean {
        return true;
    }

    private getReferrers(referrerInfos: any[]): string[] {
        // HACK: We need to ensure the list of ids contains at least one item, otherwise the query expression results in an invalid odata filter query segment.
        // TODO: As a Developer I want to prevent the execution of the loadQuestions method if the list of ids is empty so that we don't need to send the odata fake filter to the server. 
        return referrerInfos.length == 0 ? ['-1'] : referrerInfos.map(value => value.referrerId);
    }

    private loadQuestions(
        questions: KnockoutObservableArray<QuestionInfo>,
        query: (it: Disco.Ontology.Post) => boolean,
        args: any,
        entityFactory: (it: Disco.Ontology.Post) => QuestionInfo): $data.IPromise<Disco.Ontology.Post[]> {

        questions([]);

        var additionalImports: string[] = ['RefersTo', 'Ratings', 'ReferredFrom', 'ModifiedBy.Author', 'Tags.Description'];

        var promise = this.loadPosts(query, args, (data: Disco.Ontology.Post[]) => {
            data.forEach((post: Disco.Ontology.Post) => {
                questions.push(entityFactory(post));
            });
        }, additionalImports);

        return promise;
    }
}

return new Discussion();